#!/bin/sh
#
# Copyright (C) 2022 by Friedemann Bartels
#
# This file may be distributed and/or modified under the
# conditions of the LaTeX Project Public License, either
# version 1.3c of this license or (at your option) any later
# version.  The latest version of this license is in:
#
#    http://www.latex-project.org/lppl.txt
#
# and version 1.3c or later is part of all distributions of
# LaTeX version 2008/05/04 or later.
#

version=1.0.2

OK="$( tput setaf 2 )•$( tput sgr0 )"
FAILED="$( tput setaf 1 )$( tput bold )x$( tput sgr0 )"
NEW="$( tput setaf 3 )$( tput bold )+$( tput sgr0 )"
REMOVED="$( tput setaf 5 )$( tput bold )-$( tput sgr0 )"
ERROR="$( tput setaf 1 )$( tput bold )!$( tput sgr0 )"

_playok() {
  if [ -f /System/Library/Sounds/Glass.aiff ]; then
    afplay /System/Library/Sounds/Glass.aiff >/dev/null 2>&1
  fi
}

_playhm() {
  if [ -f /System/Library/Sounds/Basso.aiff ]; then
    afplay /System/Library/Sounds/Basso.aiff >/dev/null 2>&1
  fi
}

_playerror() {
  if [ -f /System/Library/Sounds/Sosumi.aiff ]; then
    afplay /System/Library/Sounds/Sosumi.aiff >/dev/null 2>&1
  fi
}

_gettime() {
  if command -v gdate &> /dev/null; then
    echo $( gdate +%s%3N )
  else
    if command -v date &> /dev/null; then
      milliseconds=$( date +%3N )
      if [ $milliseconds = 3N ]; then
        echo $(( SECONDS * 1000 ))
      else
        echo $( date +%s%3N )
      fi
    else
      echo $(( SECONDS * 1000 ))
    fi
  fi
}

_starttimer() {
  starttime=$( _gettime )
}

_stoptimer() {
  endtime=$( _gettime )

  elapsedtime=$(( endtime - starttime ))
  elapsedseconds=$(( elapsedtime / 1000 ))
  elapsedmilliseconds=$(( elapsedtime % 1000 ))
  elapsed=$elapsedseconds.$( printf "%03d" "$elapsedmilliseconds" )
}

test() {
  _starttimer
  tmpdir=$( mktemp -d 2>/dev/null )/textest$( date "+%Y%m%d%H%M%S" )$RANDOM
  mkdir $tmpdir

  if [ $3 = true ] && [ -d cache ]; then
    rm -rf cache
  fi

  okcount=0
  failedcount=0
  newcount=0
  removedcount=0
  fileokcount=0
  filefailedcount=0
  defaultfiller=23

  echo ""
  for entry in *$5*.tex
  do
    if [ $entry != "*$5*.tex" ]; then
      name=$( echo $entry | sed -e 's/\.tex$//g' )
      if [ ${name:0:1} != _ ]; then
        printf "$( tput sgr0 )$name"

        runtwice=0
        if [ "${name:$(( ${#name} - 2 )):2}" = "@2" ]; then
          runtwice=1
        fi

        if [ "$4" != "xelatex" ]; then
          name="$name.$4"
        fi

        passed=1
        rm -f $name.failed.*

        cp $entry $tmpdir/$name.tex
        shell=""
        if [ $1 = true ]; then
          shell="--shell-escape"
        fi
        $4 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$name.tex > /dev/null
        if [ $? -eq 1 ]; then
          passed=0
        fi
        if [ $runtwice -eq 1 ]; then
          $4 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$name.tex > /dev/null
          if [ $? -eq 1 ]; then
            passed=0
          fi
        fi

        magick convert -density $2 -quiet $tmpdir/$name.pdf $tmpdir/$name.png
        if [ -f "$tmpdir/$name.png" ]; then
          mv $tmpdir/$name.png $tmpdir/$name-0.png
        fi

        if [ -f "$name.approved.pdf" ]; then
          magick convert -density $2 -quiet $name.approved.pdf $tmpdir/$name.approved.png
          if [ -f "$tmpdir/$name.approved.png" ]; then
            mv $tmpdir/$name.approved.png $tmpdir/$name.approved-0.png
          fi
        fi

        if [ $passed -eq 0 ]; then
          filler=$(( defaultfiller - ${#name} - 2 ))
        else
          filler=$(( defaultfiller - ${#name} ))
        fi
        while [ 0 -le $filler ]; do
          printf " "
          filler=$(( filler - 1 ))
        done
        if [ $passed -eq 0 ]; then
          printf " $ERROR"
        fi

        index=0
        while [ -f "$tmpdir/$name-$index.png" ]; do
          if [ -f "$tmpdir/$name.approved-$index.png" ]; then
            changedpixels=$( magick compare -quiet -metric AE $tmpdir/$name-$index.png $tmpdir/$name.approved-$index.png null: 2>&1 )
            if [ $changedpixels -eq 0 ]; then
              okcount=$(( okcount + 1 ))
              printf " $OK"
            else
              failedcount=$(( failedcount + 1 ))
              passed=0
              printf " $FAILED"
              magick compare -quiet $tmpdir/$name-$index.png $tmpdir/$name.approved-$index.png $name.failed.$(( index + 1 )).png
              if [ -f "$tmpdir/$name.pdf" ]; then
                mv $tmpdir/$name.pdf $name.failed.pdf
              fi
            fi
          else
            newcount=$(( newcount + 1 ))
            passed=0
            printf " $NEW"
            mv $tmpdir/$name-$index.png $name.failed.$(( index + 1 )).png
            if [ -f "$tmpdir/$name.pdf" ]; then
              mv $tmpdir/$name.pdf $name.failed.pdf
            fi
          fi
          index=$(( index + 1 ))
          if [ `expr $index % 20` -eq 0 ] && [ -f "$tmpdir/$name-$index.png" ]; then
            echo ""
            filler=$defaultfiller
            while [ 0 -le $filler ]; do
              printf " "
              filler=$(( filler - 1 ))
            done
          fi
        done
        while [ -f "$tmpdir/$name.approved-$index.png" ]; do
          removedcount=$(( removedcount + 1 ))
          passed=0
          printf " $REMOVED"
          mv $tmpdir/$name.approved-$index.png $name.failed.$(( index + 1 )).png
          if [ -f "$tmpdir/$name.pdf" ]; then
            mv $tmpdir/$name.pdf $name.failed.pdf
          fi
          index=$(( index + 1 ))
          if [ `expr $index % 20` -eq 0 ] && [ -f "$tmpdir/$name.approved-$index.png" ]; then
            echo ""
            filler=$defaultfiller
            while [ 0 -le $filler ]; do
              printf " "
              filler=$(( filler - 1 ))
            done
          fi
        done

        if [ $passed -eq 1 ]; then
          fileokcount=$(( fileokcount + 1 ))
        else
          filefailedcount=$(( filefailedcount + 1 ))
        fi

        echo ""
      fi
    fi
  done

  rm -rf $tmpdir
  _stoptimer
  seconds=$( printf "%.1f" "$elapsed" )

  filecount=$(( fileokcount + filefailedcount ))

  if [ $filecount -gt 0 ]; then
    echo ""
  fi
  if [ $filecount -eq $fileokcount ]; then
    if [ $filecount -eq 0 ]; then
      _playhm &
      echo "$( tput sgr0 )Tested 0 files 🧐."
    else
      _playok &
      echo "$( tput sgr0 )Successfully tested $filecount files in $seconds seconds 🎉."
    fi
    echo ""
  else
    _playerror &
    echo "$( tput sgr0 )Tested $filecount files in $seconds seconds."
    echo ""

    if [ $fileokcount -gt 0 ]; then
      filler=$(( 6 + ${#filecount} - ${#fileokcount} ))
      while [ 0 -le $filler ]; do
        printf " "
        filler=$(( filler - 1 ))
      done
      printf "$( tput setaf 2 )$fileokcount OK$( tput sgr0 )"
      echo ""
    fi
    if [ $filefailedcount -gt 0 ]; then
      filler=$(( 6 + ${#filecount} - ${#filefailedcount} ))
      while [ 0 -le $filler ]; do
        printf " "
        filler=$(( filler - 1 ))
      done
      printf "$( tput setaf 1 )$filefailedcount Failed$( tput sgr0 )"
      echo ""
    fi
    echo ""
    exit 1
  fi
}

approve() {
  filecount=0
  
  suffix=""
  if [ "$1" != "xelatex" ]; then
    suffix=".$1"
  fi

  echo ""
  for entry in *$2*$suffix.failed.pdf
  do
    if [ $entry != "*$2*$suffix.failed.pdf" ]; then
      filecount=$(( filecount + 1 ))
      name=$( echo $entry | sed -e "s/$suffix.failed.pdf//" )
      echo $name
      rm -f $name$suffix.approved.pdf
      if [ -f "$name.aux" ]; then
        cp $name$suffix.failed.pdf $name$suffix.approved.pdf
      else
        mv $name$suffix.failed.pdf $name$suffix.approved.pdf
      fi

      rm -f $name$suffix.failed.*
    fi
  done

  if [ $filecount -gt 0 ]; then
    echo ""
  fi
  if [ $filecount -eq 1 ]; then
    echo "Approved 1 file."
  else
    echo "Approved $filecount files."
  fi
  echo ""
}

compare() {
  filea=$2
  fileb=$3
  tmpdir=$( mktemp -d 2>/dev/null)/textest$( date "+%Y%m%d%H%M%S" )$RANDOM
  mkdir $tmpdir

  namea=$( echo $filea | sed -e 's/\.\///g' | sed -e 's/\.pdf$//g' )
  nameb=$( echo $fileb | sed -e 's/\.\///g' | sed -e 's/\.pdf$//g' )
  basenamea=$(basename $namea)
  basenameb=$(basename $nameb)

  rm -f $namea.diff.*.png

  spacer=""
  if [ -f "$filea" ]; then
    if [ -f "$fileb" ]; then
      magick convert -density $1 -quiet $filea $tmpdir/$basenamea.png
      if [ -f "$tmpdir/$basenamea.png" ]; then
        mv $tmpdir/$basenamea.png $tmpdir/$basenamea-0.png
      fi
      magick convert -density $1 -quiet $fileb $tmpdir/$basenameb.png
      if [ -f "$tmpdir/$basenameb.png" ]; then
        mv $tmpdir/$basenameb.png $tmpdir/$basenameb-0.png
      fi

      index=0
      while [ -f "$tmpdir/$basenamea-$index.png" ]; do
        if [ -f "$tmpdir/$basenameb-$index.png" ]; then
          changedpixels=$( magick compare -quiet -metric AE $tmpdir/$basenamea-$index.png $tmpdir/$basenameb-$index.png null: 2>&1 )
          if [ $changedpixels -eq 0 ]; then
            printf "$spacer$OK"
            spacer=" "
          else
            printf "$spacer$FAILED"
            spacer=" "
            magick compare -quiet $tmpdir/$basenamea-$index.png $tmpdir/$basenameb-$index.png $namea.diff.$(( index + 1 )).png
          fi
        else
          printf "$spacer$NEW"
          spacer=" "
        fi
        index=$(( index + 1 ))
      done
      while [ -f "$tmpdir/$basenameb-$index.png" ]; do
        printf "$spacer$REMOVED"
        spacer=" "
        index=$(( index + 1 ))
      done
      echo ""
    else
      echo "File $fileb not found."
    fi
  else
    echo "File $filea not found."
  fi

  rm -rf $tmpdir
}

perf() {
  if [ -f $4 ]; then
    tmpdir=$( mktemp -d 2>/dev/null )/textestperf$( date "+%Y%m%d%H%M%S" )$RANDOM
    mkdir $tmpdir
    basename=$(basename $4)

    if [ $1 = true ] && [ -d cache ]; then
      rm -rf cache
    fi

    cp $4 $tmpdir
    shell=""
    if [ $2 = true ]; then
      shell="--shell-escape"
    fi
    _starttimer
    $3 -interaction=batchmode $shell --output-directory $tmpdir $tmpdir/$basename > /dev/null
    _stoptimer
    echo "$elapsed"

    rm -rf $tmpdir
  else
    echo "File $4 not found."
  fi
}

usage() {
  cat <<HELP_USAGE
usage: xput test [-cs] [-e ENGINE] [-d DENSITY] [PATTERN]
       xput approve [-e ENGINE] [PATTERN]
       xput compare [-d DENSITY] FILE FILE
       xput perf [-cs] [-e ENGINE] FILE
       xput --help
       xput --version

   -c          clear cache
   -s          enable shell escape
   -d DENSITY  density in ppi (default 72)
   -e ENGINE   latex engine (default xelatex)

   Running \`xput test\` creates pdf files for all tex files matching the
   pattern and performs a visual regression test. If no pattern is specified,
   all files in the current directory are tested except for files whose names
   begin with an underscore.

   Running \`xput approve\` replaces the reference files with the test files.

   Running \`xput compare\` creates a visual diff for two pdf files.

   Running \`xput perf\` measures the time for creating a pdf file from the
   given tex file.

   Running \`xput --help\` returns this man page.

   Running \`xput --version\` returns the Xput version.
HELP_USAGE
}

case "$1" in
test)
  shift

  density=72
  shell=false
  clear=false
  engine=xelatex

  while getopts ":e:d:sc" arg; do
    case "${arg}" in
      c)
        clear=true
        ;;
      d)
        density="${OPTARG}"
        ;;
      e)
        engine="${OPTARG}"
        ;;
      s)
        shell=true
        ;;
      ?)
        echo "Invalid option: -${OPTARG}."
        ;;
    esac
  done

  shift "$(( OPTIND - 1 ))"

  test $shell $density $clear $engine $1
  ;;
approve)
  shift
  engine=xelatex

  while getopts ":e:" arg; do
    case "${arg}" in
      e)
        engine="${OPTARG}"
        ;;
      ?)
        echo "Invalid option: -${OPTARG}."
        ;;
    esac
  done

  shift "$(( OPTIND - 1 ))"

  approve $engine $1
  ;;
compare)
  shift

  density=72

  while getopts ":d:" arg; do
    case "${arg}" in
      d)
        density="${OPTARG}"
        ;;
      ?)
        echo "Invalid option: -${OPTARG}."
        ;;
    esac
  done

  shift "$(( OPTIND - 1 ))"

  compare $density $1 $2
  ;;
perf)
  shift

  shell=false
  clear=false
  engine=xelatex

  while getopts ":e:sc" arg; do
    case "${arg}" in
      c)
        clear=true
        ;;
      e)
        engine="${OPTARG}"
        ;;
      s)
        shell=true
        ;;
      ?)
        echo "Invalid option: -${OPTARG}."
        ;;
    esac
  done

  shift "$(( OPTIND - 1 ))"

  perf $clear $shell $engine $1
  ;;
--version)
  echo Xput $version
  ;;
*)
  usage
esac
